"use strict";

var C = require('./common'),
    path = require('path'),
    fs = require('fs'),
    xmldom = require('xmldom'),
    DOMParser = xmldom.DOMParser;

function childNodesPatch(childNodes, keepText) {
    var nodes = [];

    C._.each(childNodes, function (node) {

        var name = node.nodeName;

        if (name.charAt(0) == '#') {

            if (name === '#comment') return;

            if (name === '#cdata-section') {
                nodes.push(node);
                return;
            }

            if (name === '#text') {
                if (keepText) {
                    var v = node.data.trim();
                    if (v == '') return;
                } else return;
            }
        }

        nodes.push(node);
    });

    return nodes;
}

var TaskEngine = function(app, moduleName) {
    this.app = app;
    this.cwd = app.root;
    this.etc = app.config('modules.' + moduleName);

    this.moduleName = moduleName;

    if (!this.etc) {
        throw new Error('Web module [' + moduleName + '] not exist!');
    }

    var taskFile = './web_modules/' + this.moduleName + '/noding-tasks.xml';
    if (!fs.existsSync(taskFile)) {
        throw new Error('"noding-tasks.xml" not found in web module [' + this.moduleName + ']!');
    }

    var xml = fs.readFileSync(taskFile, 'utf8');

    this.doc = new DOMParser().parseFromString(xml, 'text/xml');

    this.projectName = this.doc.documentElement.getAttribute('name');

    console.log('Loading tasks of [' + this.projectName + ']...');

    var targets = this.doc.documentElement.getElementsByTagName('target');
    this.targets = {};

    var self = this;

    C._.each(targets, function (target) {
        var targetName = target.getAttribute('name');
        if (!targetName) {
            throw new Error('Missing target name at line: ' + target.lineNumber + '!');
        }

        if (targetName in self.targets) {
            throw new Error('Duplicate target name: ' + targetName + '!');
        }

        self.targets[targetName] = target;
    });
};

TaskEngine.prototype.run = function (target, cb) {
    target || (target = this.doc.documentElement.getAttribute('default'));

    if (!target) {
        throw new Error('No target to run.');
    }

    this.runTarget(target, function (err) {
        if (err) throw err;

        console.log('done.');
        cb && cb();
    });
};

TaskEngine.prototype.runTarget = function (target, cb) {
    var node = this.targets[target];
    if (!node) {
        return cb('Target [' + target + '] not found!');
    }

    console.log('Loading target [' + target + ']...');

    var self = this;

    function processTargetTasks() {
        var taskNodes = childNodesPatch(node.childNodes);

        console.log('Run target [' + target + ']...');

        return C.async.eachSeries(taskNodes, function (n, c) {
            self.runTask(n, c);
        }, function (err) {
            if (err) return cb(err);

            cb();
        });
    }

    //run dependencies
    var depends = node.getAttribute('depends');
    if (depends) {
        depends = depends.split(',');

        return C.async.eachSeries(depends, function (t, c) {
            t = t.trim();
            console.log('Target [' + target + '] depends on [' + t + '].');

            self.runTarget(t, c);
        }, function (err) {
            if (err) return cb(err);

            processTargetTasks();
        });
    }

    processTargetTasks();
};

TaskEngine.prototype.runTask = function (taskNode, cb) {
    var self = this;

    var context = {
        env: ENV,
        modulePath: './web_modules/' + this.moduleName,
        cwd: this.cwd,
        etc: function (path) {
            return C.getValueByPath(self.etc,  'options.' + path);
        }
    };

    var engine = C._.extend(context, {
        parseValue: function (value) {
            return C.H.renderSwig(value, context);
        }
    });

    var taskName = taskNode.nodeName;
    C.excludeDotDotInPath('task name', taskName);

    console.log('Running task [' + taskName + ']...');

    var attributes = C.objectProjection(taskNode.attributes, 'name', 'value');
    var contentNodes = childNodesPatch(taskNode.childNodes, true);
    var content = contentNodes.length == 0 ? null : (contentNodes.length > 1 ? contentNodes : contentNodes[0].data);

    var taskFile = path.join(this.cwd, './web_modules/' + this.moduleName + '/tasks/' + taskName + '.js');
    if (!fs.existsSync(taskFile)) {
        taskFile = path.join(__dirname, '../tasks/' + taskName + '.js');
        if (!fs.existsSync(taskFile)) {
            return cb('Task [' + taskName + '] not found!');
        }
    }

    var task = require(taskFile);
    task(engine, attributes, content, function (err) {
        if (err) return cb(err);

        console.log('Task [' + taskName + '] is done.');
        cb();
    });
};

module.exports = TaskEngine;